Borland Online And The Cobb Group Present:


February, 1996 - Vol. 3 No. 2

OWL Programming - Creating your own validation classes

by Mike Stephenson

One of the hallmarks of good code is its ability to handle bad user input gracefully. As a developer, you can't assume that your users will always enter correct or meaningful data. The work of checking user input, called validation, can be a real chore, but fortunately, OWL provides tools to help you with this task.

However, if you've ever used these tools, you know that they sometimes behave in odd ways. For example, if the user enters an invalid character, it appears briefly on the screen and then vanishes. Users tend to find this behavior distracting and even annoying.

In this article, we'll show you how to write your own validation classes. By taking advantage of the power already inherent in OWL's validation classes, you'll see that creating custom validators is easier than you might think. We'll begin by discussing two methods you can use to validate data. Then we'll look at the programming techniques you'll need in order to put these methods into practice. Finally, we'll show you a sample application that makes use of our techniques.

Validation explanation

We're going to look at two basic methods you can use to validate user input. First, you can verify data one character at a time. This method works best in situations where only one type of input is appropriate. For example, if the user should be entering a social security number, you want your program to reject non-numeric characters as soon as the user types them.

The second method is to check data one field at a time. For example, suppose we wanted to limit user input to a number between 50 and 100. We wouldn't want to check one character at a time because each individual character would never be more than 50.

To make use of either of these methods, we're going to have to design functions that intercept specific system messages. Let's take a look at how this is done.

Validation communication

Most programs accept user data in dialog boxes using the TEdit class. This simple yet powerful class provides a great deal of functionality, such as Clipboard support, undo, and searching. To retain this functionality, we'll derive our validation class from TEdit. We'll then augment our class with functions that respond to the appropriate messages.

You need to understand the messages that will allow you to perform your own validation. Table A shows you information about the messages we'll be using­­WM_CHAR and WM_KILLFOCUS. Our validation class will respond to WM_CHAR messages to perform character-by-character validation and WM_KILLFOCUS messages to validate an entire field.

Table A: TEdit and TDialog messages
Message Response function Validation type
WM_CHAR EvChar(UINT key, UINT repeatCount, UINT flags) Character-by-character
WM_KILLFOCUS EvKillFocus(HWND hWndGetFocus) Field

Simple field validation is relatively straightforward, as you'll see in our example code. Therefore, we won't spend time discussing it in this article, though we'll show you an example. Character-by-character validation, however, requires a closer look at the various messages a key press generates.

Communication specification

When the user presses a key, the system generates a number of messages. Among these are WM_KEYDOWN, WM_KEYUP, and WM_CHAR. Of these, WM_CHAR combines the key-down and key-up messages, and you'll find that this message is the most useful for validation purposes.

WM_CHAR is useful because a response function for this message precedes output to the screen. For validation, this is exactly what we want. All we need to do is add the EV_WM_CHAR macro to our response table definition and an EvChar() function to the class we derive from TEdit. We'll put our validation code in the EvChar() function.

Fortunately, the EvChar() function is fairly uncomplicated. The function has only three arguments, and only the first one affects validation. The first argument passes the character the user pressed, in ASCII form, so you can check it against what you want to allow. If the key press is valid, just call the base class EvChar() function and let it do all the work. If the key press isn't valid, you can simply skip the call to the base class. If you want to alert the user to the bogus keystroke, you can have the system sound a beep.

One final note about key-press messages before we move on to our example code. The WM_CHAR message handles only keyboard input containing printable characters. Therefore, if you want to handle non-printable characters, such as function or arrow keys, then you'd want to handle the WM_KEYDOWN message using the EV_WM_KEYDOWN macro.

Validation illustration

Now that we've looked at the theory behind validation, let's see how to put this knowledge to use.Listings A , B, and C show the source files of a sample application we've developed that accepts user input in the form of a name and a credit card number.


Listing B: VALEDIT.H

#define DIALOG_1  1
#define IDC_EDIT1 101
#define IDC_EDIT2 102

To work with the credit card number, we've created the TCreditCardNo class. Our class looks very much like a TEdit class except for a couple of new data members and a couple of functions. We store the card number in the 17-character Card array. This length is enough to hold the longest 16-digit card numbers.


Listing C: VALEDIT.RC

#include "valedit.h"

DIALOG_1 DIALOG 5, 15, 269, 88
STYLE DS_MODALFRAME | WS_CHILD | 
   WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CLASS "bordlg_gray"
CAPTION "TEdit Derived Classes"
FONT 8, "MS Sans Serif"
{
 RTEXT "&Credit Card Number", -1, 10, 8, 99, 8
 EDITTEXT IDC_EDIT1, 118, 7, 86, 12
 RTEXT "Card &holder ", -1, 10, 22, 99, 8
 EDITTEXT IDC_EDIT2, 118, 22, 86, 12
 CONTROL "", -1, "BorShade", BSS_HDIP | BSS_LEFT | 
   WS_CHILD | WS_VISIBLE, 40, 44, 189, 3
 CONTROL "", IDOK, "BorBtn", BS_DEFPUSHBUTTON | 
   WS_CHILD | WS_VISIBLE | 
   WS_TABSTOP, 60, 53, 37, 25
 CONTROL "", IDCANCEL, "BorBtn", BS_PUSHBUTTON | 
   WS_CHILD | WS_VISIBLE | 
   WS_TABSTOP, 116, 53, 37, 25
 CONTROL "", IDHELP, "BorBtn", BS_PUSHBUTTON | 
   WS_CHILD | WS_VISIBLE | 
   WS_TABSTOP, 172, 53, 37, 25
}

Our program first uses character-by-character validation to check each digit of the credit card number. Because credit card numbers are composed only of digits, we use the EvChar() function to look at the keystrokes one at a time and reject all characters except 0-9. Then, we call the base class TEdit function to pass the digits through for processing. (Naturally, we must allow the user to enter a backspace for corrections.)

After character-by-character validation, we use a checksum routine to validate the entire credit card number as a field. All credit card numbers can be validated using a checksum value, a fact we can use to our advantage to catch most user-entered typos. Note that because each company uses numbers of different lengths, we have to know the card type (for example, American Express or Visa) in order to calculate the checksum properly.

We use TCreditCardNo class's Lookup integer array for our checksum validation by putting the validation routine itself in the CmPostEdit() function. OWL calls this function whenever the field is about to lose focus. At this point, users are indicating that they are satisfied with what they've entered and that they wish to go to the next field. We make sure they've entered the number correctly before allowing that.

The dialog box in our example application also has a Name field. We could have used a stock TEdit class, which would have done the job, but we've made it easier on our users by creating the TNameEdit class. This nifty yet very simple class converts the first character of each entered name to uppercase, allowing users to type the name without having to use the [Shift] key. The TNameEdit class makes use of the functions GetText() and SetText(), which are actually member functions of TStatic, the parent class to TEdit.

Validation specialization

Now that we've covered the basics of validation, let's look at a couple of tricks we've used to jazz up our sample application. For some fixed-length fields, such as social security numbers, phone numbers, and ZIP codes, you can help your users by automatically advancing the cursor to the next field after they type the last character. This is quite easy to do, and your users will really appreciate it.

In our example, we use the function PostMessage(WM_NEXTDLGCTL, ...) when our credit card number gets to 16 digits (or 15 for American Express). When our user fills the field, we post the next dialog box control message to advance the focus to the next field.

While we're on the subject of advancing fields, let's look at how to handle a press of the [Enter] key. The default behavior for the [Enter] key is to close a dialog box and accept the entered data. Many users, however, have come to expect the [Enter] key to close the current field and move to the next.

You can adjust this default behavior in one of two ways. With TEdit extensions, you can trap the [Enter] key and move the user to the next field. The catch, however, is that you must give your TEdit objects the Multiline style. (You can set this option from within Resource Workshop.) The [Enter] key is permitted in multiline styles so the user can move to the next line of the field without closing the dialog box.

A second, more clever way to adjust the default behavior of the [Enter] key is to call the TDialog::SetDefaultId(UINT Id) function (in TDialog::SetupWindow) to point to a non-existent button. Dialog boxes must have a default button. Ordinarily, the OK button is the default and the dialog box closes when the user presses [Enter]. If you have set the default ID to a nonexistent button, pressing [Enter] does nothing­­the dialog box stays open. You can then have a response function that posts the next dialog box control message (PostMessage(WM_NEXTDLGCTL, (WPARAM)0, FALSE). This causes the [Enter] key to behave as a tab key.

Conclusion

We've shown you how to modify OWL's validator classes so your applications will behave the way you want. Using this technique, you can develop incredibly flexible classes of validators for data of any variety, even if the validation requires table lookups or other functions. This flexibility will help you keep your interface sharp and your users happy.

Mike Stephenson is a professional C++ programmer and an active volunteer on Borland's BCPPWIN CompuServe forum. You can reach Mike via CompuServe at 70031,1607.

Return to the Borland C++ Developer's Journal index

Subscribe to the Borland C++ Developer's Journal


Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.